/*
 * com.c
 *
 *  Created on: 8 Nov 2017
 *      Author: Standa
 */

#include "com.h"
#include "error.h"
#include "main.h"

#include <stdint.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include "commands.h"
#include "usart.h"
#include "can.h"
#include "crc.h"
#include "tim.h"


#define COM_RXBUFSIZE				1024								//Must be power of two because of some math magic
#define COM_RXBUFTAILMASK			( COM_RXBUFSIZE - 1 )
#define COM_ENDOFCMDCHAR			'\n'

#define COM_TXBUFSIZE				2048								//Must be power of two because of some math magic
#define COM_TXBUFHEADMASK			( COM_TXBUFSIZE - 1 )

#define COM_SIG_CMDRECEIVED			0x01								//Complete command (\n) received via UART/USB
#define COM_SIG_TRANSMIT 			0x02								//Data to transmit available in UART/USB bufer
#define COM_SIG_CANLASTERROR		0x04								//CAN error interrupt occured - forward via UART/USB
#define COM_SIG_CANMSGRECEIVED		0x08								//CAN msg received - forward it via UART/USB



static uint32_t StatusRegister = 0;
static char EvtStr[256];

static uint32_t CANLastError = 0;
static CAN_RxHeaderTypeDef CANRxHeader;
static uint8_t CANRxData[8];

static uint8_t RxSingleByteBuffer;										//Kind of hack needed for HAL to transfer each received byte into circular buffer
static uint8_t	com_RxBuffer[COM_RXBUFSIZE];
static uint32_t com_RxHead = 0;
static uint32_t com_RxTail = 0;
static int32_t com_RxCmdCnt = 0;
static int32_t com_HandledRxCmdCnt = 0;
static uint8_t com_Cmd[COM_RXBUFSIZE/2];

static uint8_t com_TxBuffer[COM_TXBUFSIZE];
static uint32_t com_TxHead = 0;
static uint32_t com_TxTail = 0;
static char com_TmpStr[COM_TXBUFSIZE/2];

static uint8_t IsByteOrderReversed = 0;
static uint16_t Baudrate_kbps = 0;
static uint8_t TxInterruptionEnabled = 0;


/*
 * Initialize communication module (UART and CAN).
 */
err_Td com_Init(void){
	LL_EXTI_DisableIT_0_31(EXTI9_5_IRQn);
	com_SetCANSpeed(250);
	HAL_UART_Receive_IT(&huart1, &RxSingleByteBuffer, 1);
	return err_Td_Ok;
}


/*
 * Sets CAN baudrate to 250kbps, 500kbps or 1000kbps
 * See http://www.bittiming.can-wiki.info/
 */
err_Td com_SetCANSpeed(uint16_t baudrate_kbps){
	CAN_FilterTypeDef  sFilterConfig;

	HAL_CAN_DeInit(&hcan);

	hcan.Instance = CAN1;
	hcan.Init.Mode = CAN_MODE_NORMAL;
	hcan.Init.SyncJumpWidth = CAN_SJW_1TQ;
	hcan.Init.TimeSeg2 = CAN_BS2_2TQ;

	if(baudrate_kbps == 250){
		hcan.Init.Prescaler = 9;
		hcan.Init.TimeSeg1 = CAN_BS1_13TQ;
	}
	else if(baudrate_kbps == 500){
		hcan.Init.Prescaler = 4;
		hcan.Init.TimeSeg1 = CAN_BS1_15TQ;
	}
	else if(baudrate_kbps == 1000){
		hcan.Init.Prescaler = 2;
		hcan.Init.TimeSeg1 = CAN_BS1_15TQ;
	}
	else{
		Baudrate_kbps = 0;
		return err_Td_Param;
	}

	hcan.Init.TimeTriggeredMode = DISABLE;
	hcan.Init.AutoBusOff = DISABLE;
	hcan.Init.AutoWakeUp = DISABLE;
	hcan.Init.AutoRetransmission = DISABLE;
	hcan.Init.ReceiveFifoLocked = DISABLE;
	hcan.Init.TransmitFifoPriority = DISABLE;
	if (HAL_CAN_Init(&hcan) != HAL_OK){
		Baudrate_kbps = 0;
		return err_Td_Init;
	}

	/* Configure the CAN Filter */
	sFilterConfig.FilterBank = 0;
	sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK;
	sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT;
	sFilterConfig.FilterIdHigh = 0x0000;
	sFilterConfig.FilterIdLow = 0x0000;
	sFilterConfig.FilterMaskIdHigh = 0x0000;
	sFilterConfig.FilterMaskIdLow = 0x0000;
	sFilterConfig.FilterFIFOAssignment = CAN_RX_FIFO0;
	sFilterConfig.FilterActivation = ENABLE;
	sFilterConfig.SlaveStartFilterBank = 14;

	if (HAL_CAN_ConfigFilter(&hcan, &sFilterConfig) != HAL_OK)	{
		Baudrate_kbps = 0;
		return err_Td_Disabled;
	}

	HAL_CAN_ActivateNotification(&hcan, CAN_IT_RX_FIFO0_MSG_PENDING | CAN_IT_TX_MAILBOX_EMPTY | CAN_IT_ERROR_WARNING | CAN_IT_ERROR_PASSIVE | CAN_IT_BUSOFF | CAN_IT_LAST_ERROR_CODE | CAN_IT_ERROR);
	HAL_CAN_Start(&hcan);
	Baudrate_kbps = baudrate_kbps;
	return err_Td_Ok;
}


/*
 * Returns set baudrate of CAN in kbps.
 */
uint16_t com_GetBaudrate(void){
	return Baudrate_kbps;
}


/*
 * Configures CAN TX interruption for next transmitted message. Relevant only for transmitted messages.
 * There is approx 2us inherent delay which adds to specified start_us delay.
 * Both parameters shall be greater than zero and their sum shall be lower than 2^16 = 65536.
 */
err_Td com_SetTxInterruption(uint16_t start_us, uint16_t duration_us){
	if( (start_us + duration_us) > (UINT16_MAX) || start_us == 0 || duration_us == 0 ){
		return err_Td_Param;
	}

	LL_EXTI_DisableIT_0_31(EXTI9_5_IRQn);

	LL_TIM_SetCounter(TIM3, 0);
	LL_TIM_OC_SetCompareCH1(TIM3, start_us);
	LL_TIM_SetAutoReload(TIM3, start_us + duration_us-1);
	LL_TIM_SetOnePulseMode(TIM3, LL_TIM_ONEPULSEMODE_SINGLE);
	LL_TIM_ClearFlag_UPDATE(TIM3);
	LL_TIM_EnableIT_UPDATE(TIM3);

	TxInterruptionEnabled = 1;
	__HAL_GPIO_EXTI_CLEAR_IT(CAN_TX_SENSE_Pin);
	LL_EXTI_EnableIT_0_31(EXTI9_5_IRQn);			//Enable CAN TX interrupt and which will start the timer and handle the interruption of CAN message
	return err_Td_Ok;
}


/*
 * Called from EXTI falling edge interrupt on CAN_TX pin. Should start interuption timing and execution.
 */
void com_CanTxPinChanged(void){
	if(TxInterruptionEnabled){															//If interruption is enabled
		LL_EXTI_DisableIT_0_31(EXTI9_5_IRQn);
		TxInterruptionEnabled = 0;
		LL_TIM_EnableCounter(TIM3);														//Enable timer to handle the interruption

		if(TIM3->CCR1 == 0){															//And if interruption should start immediately set corresponding pins
			LL_GPIO_SetOutputPin(CANH_SHORT_GPIO_Port, CANH_SHORT_Pin | CANL_SHORT_Pin);
		}
		else{
			LL_TIM_ClearFlag_CC1(TIM3);
			LL_TIM_EnableIT_CC1(TIM3);
		}
	}
}


/*
 * Called from TIM3 compare interrupt. Delay is over so it will start the interruption pulse.
 */
void com_DelayElapsed(void){
	LL_GPIO_SetOutputPin(CANH_SHORT_GPIO_Port, CANH_SHORT_Pin | CANL_SHORT_Pin);
	LL_TIM_ClearFlag_CC1(TIM3);
	LL_TIM_DisableIT_CC1(TIM3);
}


/*
 * Called from TIM3 update interrupt. Interruption pulse is over so release the switches.
 */
void com_DurationPassed(void){
	LL_GPIO_ResetOutputPin(CANH_SHORT_GPIO_Port, CANH_SHORT_Pin | CANL_SHORT_Pin);
	LL_TIM_DisableCounter(TIM3);
	LL_TIM_ClearFlag_UPDATE(TIM3);
	LL_TIM_DisableIT_UPDATE(TIM3);
}


/*
 * Set byte order of CAN payloads for both RX and TX directions.
 */
void com_SetByteOrder(uint8_t mode){
	if(mode) IsByteOrderReversed = 1;
	else IsByteOrderReversed = 0;
}


/*
 * Returns byte order of CAN payloads.
 */
uint8_t com_GetByteOrder(void){
	return IsByteOrderReversed;
}


/*
 * Send data to PC via UART.
 */
err_Td com_SendF(char *format, ...){
	va_list va;
	int32_t i;

	va_start(va, format);												//Start reading of parameters
	vsnprintf(com_TmpStr, sizeof(com_TmpStr), format, va);				//Format new string and save its length
	va_end(va);															//End of reading parameters

	if( strlen(com_TmpStr) < sizeof(com_TmpStr) ){						//Check if formated string including terminating zero fits into buffer
		for( i=0; i<strlen(com_TmpStr); i++ ){							//Copy byte by byte into tx buffer
			com_TxBuffer[ com_TxHead++ ] = com_TmpStr[i];
			com_TxHead &= COM_TXBUFHEADMASK;
		}
		StatusRegister |= COM_SIG_TRANSMIT;
		return err_Td_Ok;
	}
	else{
		return err_Td_Overflow;
	}

}


/*
 * Copy data from communication peripheral buffer into this module circular buffer where they can be parsed.
 * User must make sure only one way to access receive buffer is used (so only USART OR USB access in this case)
 */
err_Td com_Receive(uint8_t* buf, uint32_t len){
	int32_t i;
	if( buf && len <= COM_RXBUFSIZE ){
		for( i=0; i<len; i++ ){
			com_RxBuffer[com_RxHead++] = buf[i];
			com_RxHead &= COM_RXBUFTAILMASK;
			if( buf[i] == COM_ENDOFCMDCHAR ){
				com_RxCmdCnt++;											//Increment counter of received commands in buffer
				StatusRegister |= COM_SIG_CMDRECEIVED;					//Set signal to receiving task
			}
		}
		return err_Td_Ok;
	}
	else if(len > COM_RXBUFSIZE){
		return err_Td_Overflow;
	}
	else{
		return err_Td_Null;
	}
}


/*
 * Parsing of incoming messages.
 */
static void HandleRecCmd(void){
	uint8_t tmp;
	uint32_t i = 0;

	while( ( ( tmp = com_RxBuffer[ (com_RxTail++) & COM_RXBUFTAILMASK ] ) != COM_ENDOFCMDCHAR ) ){
		if( i < ( sizeof(com_Cmd) - 2 ) ){								//Take into account terminating zero
			com_Cmd[i++] = tmp;											//Copy byte by byte to command buffer until end of command byte is found or command buffer is full
		}
		else{
			break;
		}
	}

	com_Cmd[i++] = COM_ENDOFCMDCHAR;
	com_Cmd[i] = 0;

	cmd_Handle((char*)com_Cmd);
}


/*
 * Transmits data from Tx circular buffer either in one part if data is linear or in two parts if data is broken by circular buffers.
 */
static uint32_t TxCnt;
static void TransmitTxBuffer(void){

	if( com_TxHead != com_TxTail ){												//If there is data to transfer
		if( com_TxHead > com_TxTail ){											//If all data in circular buffer is linear
			TxCnt = com_TxHead - com_TxTail;


			if( HAL_UART_Transmit_IT(&huart1, com_TxBuffer + com_TxTail, TxCnt) != HAL_OK ){	//Transmit and if it failed
				StatusRegister |= COM_SIG_TRANSMIT;						//Request repeated transmission
				return;
			}
			com_TxTail = com_TxHead;											//Move tail to head position (errors are ignored)
		}
		else if( com_TxHead < com_TxTail ){										//If data is separated by circular buffer end
			TxCnt = COM_TXBUFSIZE - com_TxTail;
			if( HAL_UART_Transmit_IT(&huart1, com_TxBuffer + com_TxTail, TxCnt) != HAL_OK ){	//Transmit and if it failed
				StatusRegister |= COM_SIG_TRANSMIT;						//Request repeated transmission
				return;
			}

			com_TxTail = 0;														//Transmit first end of buffer data part and then linear part from beginning of circular buffer
			StatusRegister |= COM_SIG_TRANSMIT;						//Request repeated transmission
		}
	}
}


/*
 * Handles all the task required by this module (UART and CAN transmission and reception).
 */
void com_LoopTask(void){
	uint32_t CalCRC, i;

	if( StatusRegister & COM_SIG_TRANSMIT ){
		StatusRegister &= ~COM_SIG_TRANSMIT;
		TransmitTxBuffer();
	}

	if( StatusRegister & COM_SIG_CMDRECEIVED ){
		StatusRegister &= ~COM_SIG_CMDRECEIVED;
		while( com_RxCmdCnt > com_HandledRxCmdCnt ){
			HandleRecCmd();
			com_HandledRxCmdCnt++;
		}
	}

	if( StatusRegister & COM_SIG_CANLASTERROR ){
		StatusRegister &= ~COM_SIG_CANLASTERROR;

		//[CRC] evt [EvtName] [TimeStamp] [CAN_ESR_UINT32_HEX] [REC_UINT8_HEX] [TEC_UINT8_HEX] [LEC_UINT7_HEX] [BOFF_UINT1_HEX] [EPVF_UINT1_HEX] [CAN_EWGF_UINT1_HEX]\r\n
		i = snprintf(EvtStr, sizeof(EvtStr), "evt err %u %08x\t%04x\t%04x\t%02x\t%02x\t%02x\t%02x\r\n",
				(unsigned int)HAL_GetTick(), (unsigned int)CANLastError,
				(unsigned int)(CANLastError>>24) & 0xFF, (unsigned int)(CANLastError>>16) & 0xFF, (unsigned int)(CANLastError>>4) & 0x07,
				(unsigned int)(CANLastError>>2) & 0x1, (unsigned int)(CANLastError>>1) & 0x1, (unsigned int)(CANLastError>>0) & 0x1);
		CalCRC = HAL_CRC_Calculate(&hcrc, (uint32_t*)(EvtStr), i);
		com_SendF("%08x %s", CalCRC, EvtStr);

	}

	if( StatusRegister & COM_SIG_CANMSGRECEIVED ){
		StatusRegister &= ~COM_SIG_CANMSGRECEIVED;

		//[CRC] evt canrx [TimeStamp] [StdId_UINT12_HEX] [DLC_UINT4_HEX] [Byte7_HEX] [Byte6_HEX] [Byte5_HEX] [Byte4_HEX] [Byte3_HEX] [Byte2_HEX] [Byte1_HEX] [Byte0_HEX]\r\n

		if(com_GetByteOrder()){		//If byte order mode is inverted
			i = snprintf(EvtStr, sizeof(EvtStr), "evt canrx %u\t%03x\t%01x\t%02x\t%02x\t%02x\t%02x\t%02x\t%02x\t%02x\t%02x\t\r\n",
					(unsigned int)HAL_GetTick(),
					(unsigned int)(CANRxHeader.StdId), (unsigned int)(CANRxHeader.DLC),
					(unsigned int)(CANRxData[7]), (unsigned int)(CANRxData[6]), (unsigned int)(CANRxData[5]), (unsigned int)(CANRxData[4]), (unsigned int)(CANRxData[3]), (unsigned int)(CANRxData[2]), (unsigned int)(CANRxData[1]), (unsigned int)(CANRxData[0]));
		}
		else{
			i = snprintf(EvtStr, sizeof(EvtStr), "evt canrx %u\t%03x\t%01x\t%02x\t%02x\t%02x\t%02x\t%02x\t%02x\t%02x\t%02x\t\r\n",
					(unsigned int)HAL_GetTick(),
					(unsigned int)(CANRxHeader.StdId), (unsigned int)(CANRxHeader.DLC),
					(unsigned int)(CANRxData[0]), (unsigned int)(CANRxData[1]), (unsigned int)(CANRxData[2]), (unsigned int)(CANRxData[3]), (unsigned int)(CANRxData[4]), (unsigned int)(CANRxData[5]), (unsigned int)(CANRxData[6]), (unsigned int)(CANRxData[7]));
		}

		CalCRC = HAL_CRC_Calculate(&hcrc, (uint32_t*)(EvtStr), i);
		com_SendF("%08x %s", CalCRC, EvtStr);

	}
}


/*
 * Save incoming UART char into buffer. Callback from HAL. Called by HAL from ISR.
 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart){
	com_Receive(&RxSingleByteBuffer, 1);
	HAL_UART_Receive_IT(&huart1, &RxSingleByteBuffer, 1);
}


/*
 * Indicate CAN error. Callback from HAL. Called by HAL from ISR.
 */
void HAL_CAN_ErrorCallback(CAN_HandleTypeDef *hcan){
	CANLastError = HAL_CAN_GetError(hcan);
	HAL_CAN_ResetError(hcan);
	StatusRegister |= COM_SIG_CANLASTERROR;
}


/*
 * Save incoming CAN message into buffer. Callback from HAL. Called by HAL from ISR.
 */
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan){
	HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &CANRxHeader, CANRxData);
	StatusRegister |= COM_SIG_CANMSGRECEIVED;
}
